package com.region.http.client.core;

import com.region.common.adapter.logging.LoggerAdapter;
import com.region.common.utils.ClassUtils;
import com.region.common.utils.StringUtils;
import com.region.core.annotation.Region;
import com.region.core.env.ExpressionPropertiesOperator;
import com.region.core.env.PropertiesOperator;
import com.region.http.client.annotation.configurer.LocalConfigurer;
import com.region.http.client.annotation.parameter.Body;
import com.region.http.client.annotation.parameter.Header;
import com.region.http.client.annotation.parameter.ParamKey;
import com.region.http.client.annotation.parameter.PathKey;
import com.region.http.client.config.ClientConfig;
import com.region.http.client.config.CustomClientConfigurer;
import com.region.http.client.config.GlobalRequestConfig;
import com.region.http.client.model.RequestMeta;
import com.region.http.client.model.RequestMeta.RequestMetaBuilder;
import com.region.http.client.pool.AsyncParameterCallable;
import com.region.loadbalancer.LoadBalancerManager;

import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author 26225
 * @date 2024/8/31 16:51
 * @description
 */
public class MetaParseHandler {

    protected LoggerAdapter loggerAdapter;

    protected final Map<Method, RequestMeta> metaData = new ConcurrentHashMap<>();

    protected ClientConfig config;

    /**
     * LoadBalancer http client
     */
    protected LoadBalancerManager loadBalancerManager;

    private MetaChain chain = new MetaChain();

    protected ExpressionPropertiesOperator expressionPropertiesOperator;

    /**
     * Environment properties operator
     */
    protected PropertiesOperator operator;

    protected CustomConfigurerRegister configurerRegister = new CustomConfigurerRegister();

    protected List<CustomClientConfigurer> customClientConfigurers;
    /**
     * Parsing interface request information and encapsulating it in a request meta-information object
     *
     * @param region
     * @param method
     * @return
     */
    protected RequestMeta parseRequestMeta(Region region, LocalConfigurer configurer, Method method, Map<Object, Object> defaultEnv) {
        Annotation[] declaredAnnotations = method.getDeclaredAnnotations();
        if (declaredAnnotations == null || declaredAnnotations.length == 0) {
            loggerAdapter.warn("No annotations on current method");
            return null;
        } else {
            RequestMetaBuilder builder = parseGlobalRequestConfig(config.getGlobalRequestConfig());
            chain.executeChain(builder, configurer);
            for (Annotation annotation : declaredAnnotations) {
                chain.executeChain(builder, annotation);
            }
            String originUrl = builder.originUrl();
            RequestMeta.MetaParameter metaParameter = parseRequestParameterMeta(method);
            if (metaParameter != null) {
                builder.metaParameter(metaParameter);
            }
            builder.type(method.getGenericReturnType());
            builder.method(method);
            StringBuilder url = new StringBuilder();
            // Encapsulated request path
            if ((region.value().endsWith("/") && !originUrl.startsWith("/")) ||
                    (!region.value().endsWith("/") && originUrl.startsWith("/"))) {
                url.append(region.value());
                url.append(originUrl);
            } else if (region.value().endsWith("/") && originUrl.startsWith("/")) {
                url.append(region.value());
                url.append(originUrl.substring(1));
            } else {
                url.append(region.value());
                url.append("/");
                url.append(originUrl.substring(1));
            }
            originUrl = url.toString();
            builder.originUrl(originUrl);
            // Creating an actuator proxy object
            if (!Registers.getInstance().existsHttpExecutor(builder.executor())) {
                Registers.getInstance().registerHttpExecutor(ClassUtils.newInstance(builder.executor()));
            }
            return builder.build();
        }
    }

    /**
     * The first parse global request config
     *
     * @param globalRequestConfig
     */
    private RequestMetaBuilder parseGlobalRequestConfig(GlobalRequestConfig globalRequestConfig) {
        RequestMetaBuilder builder = RequestMeta.builder();
        builder.requestTime(globalRequestConfig.getRequestTime());
        builder.connectTime(globalRequestConfig.getConnectTime());
        builder.readTime(globalRequestConfig.getReadTime());
        builder.executor(globalRequestConfig.getExecutor());
        builder.contentType(globalRequestConfig.getContentType());
        return builder;
    }

    /**
     * Parsing interface parameters to annotate request information and encapsulating it in a request parameter meta-information object
     *
     * @param method
     * @return
     */
    private RequestMeta.MetaParameter parseRequestParameterMeta(Method method) {
        Parameter[] parameters = method.getParameters();
        if (parameters == null || parameters.length == 0) {
            return null;
        }
        RequestMeta.MetaParameter.MetaParameterBuilder builder = RequestMeta.MetaParameter.builder();
        for (int i = 0; i < parameters.length; i++) {
            Parameter parameter = parameters[i];
            Annotation[] annotations = parameter.getAnnotations();
            if (annotations == null || annotations.length == 0) {
                if (AsyncParameterCallable.class.isAssignableFrom(parameter.getType())) {
                    builder.callable(i);
                }
                // Future addition of automatic adaptation of parameter types
                continue;
            }
            Annotation annotation = annotations[0];
            if (annotation instanceof ParamKey) {
                builder.keyIndex(((ParamKey) annotation).value(), i);
            }
            if (annotation instanceof PathKey) {
                builder.pathIndex(((PathKey) annotation).value(), i);
            }
            if (annotation instanceof Header) {
                builder.headerIndex(((Header) annotation).value(), i);
            }
            if (annotation instanceof Body) {
                builder.bodyIndex(i);
            }
        }
        return builder.build();
    }

    /**
     * Converted to a specific request address and The refresh request meta
     *
     * @param requestMeta
     * @return
     */
    protected void refreshEnvInfo(RequestMeta requestMeta) {
        // default instance status is false
        requestMeta.setStatus(false);
        requestMeta.setGroupName(null);
        List<String> envList = expressionPropertiesOperator.parseExpression(requestMeta.getOriginUrl());
        requestMeta.setEnvList(envList);
        String realUrl = getRealAddress(requestMeta);
        generateLoadBalancer(realUrl, requestMeta);
        // If it is a single instance, you need to translate the meta-request interface information into a specific request address originUrl --> url
        requestMeta.setUrl(realUrl);
        // extension configuration
        if (customClientConfigurers != null) {
            for (CustomClientConfigurer customClientConfigurer : customClientConfigurers) {
                customClientConfigurer.configureCustomRequestMeta(requestMeta.getCustomRequestMeta());
            }
        }
    }


    /**
     * Get real address by request meta
     *
     * @param requestMeta
     * @return
     */
    public String getRealAddress(RequestMeta requestMeta) {
        String realAddress = requestMeta.getOriginUrl();
        List<String> envList = requestMeta.getEnvList();
        if (envList != null && !envList.isEmpty()) {
            for (String env : envList) {
                // replace environment variable status
                StringBuilder tag = new StringBuilder();
                tag.append(ExpressionPropertiesOperator.PREFIX_TAG);
                tag.append(env);
                tag.append(ExpressionPropertiesOperator.SUFFIX_TAG);
                realAddress = StringUtils.replace(realAddress, tag.toString(), String.valueOf(this.operator.get(env)));
            }
        }
        return realAddress;
    }

    /**
     * Determine whether the load is balanced based on the actual request address
     *
     * @param url
     * @param requestMeta
     */
    private void generateLoadBalancer(String url, RequestMeta requestMeta) {
        url = url.substring(url.indexOf("://") + 3);
        if (url.indexOf("/") >= 0) {
            url = url.substring(0, url.indexOf("/"));
        }
        // Determine if it is a multiple instance or a single instance
        if (url.contains(",")) {
            // Adding a multi-instance node service group
            requestMeta.setStatus(true);
            if (this.loadBalancerManager.isShutdown()) {
                // If the load balancing manager has been shut down, try initializing
                loadBalancerManager.initialization();
            }
            // Initialize multi-instance service group information
            String groupName = this.loadBalancerManager.addSocketInfoWithNoGroupName(url.split(","));
            requestMeta.setGroupName(groupName);
        }
    }

}
